﻿// Copyright (c) egmkang wang. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace XServer.Config
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Threading;
    using System.Xml;
    using XServer.SDK;

    public class CodeGenerator
    {
        static AppDomain currentDomain = Thread.GetDomain();
        static AssemblyBuilder asm = null;
        static ModuleBuilder module = null;
        static TypeBuilder SerializationType = null;

        static Dictionary<Type, MethodInfo> methods = new Dictionary<Type, MethodInfo>();
        static MethodInfo GetAttribute = typeof(XmlTextReader).GetMethod("GetAttribute", new Type[] { typeof(string) });
        static MethodInfo XmlRead = typeof(XmlTextReader).GetMethod("Read");
        static MethodInfo XmlNodeType = typeof(XmlTextReader).GetProperty("NodeType").GetMethod;

        static CodeGenerator()
        {
            Type ConfigClassType = typeof(ConfigClass);
            var paramTypes = new Type[]
            {
                typeof(int), typeof(string), typeof(int[]), typeof(ValueTuple<int, int>),
                typeof(ValueTuple<int, int, int>), typeof(ValueTuple<int, int, int>[]),
                typeof(ValueTuple<int, int>[]),
                typeof(long),
            };

            foreach (var t in paramTypes)
            {
                methods.Add(t, ConfigClassType.GetMethod("Read", new Type[] { typeof(string), t.MakeByRefType() }));
            }

            AssemblyName myAsmName = new AssemblyName();
            myAsmName.Name = "Xml2Obj";

            asm = currentDomain.DefineDynamicAssembly(
                   myAsmName,
                   AssemblyBuilderAccess.RunAndSave);
            module = asm.DefineDynamicModule("Xml2Obj", "Xml2Obj.dll");

            SerializationType = module.DefineType("Serialization", TypeAttributes.Sealed | TypeAttributes.Public);
        }

        public static void LoadLocal(ILGenerator ilGen, int index)
        {
            switch (index)
            {
                case 0: ilGen.Emit(OpCodes.Ldloc_0); break;
                case 1: ilGen.Emit(OpCodes.Ldloc_1); break;
                case 2: ilGen.Emit(OpCodes.Ldloc_2); break;
                case 3: ilGen.Emit(OpCodes.Ldloc_3); break;
                default: ilGen.Emit(OpCodes.Ldloc_S, index); break;
            }
        }

        public static void StoreLocal(ILGenerator ilGen, int index)
        {
            switch (index)
            {
                case 0: ilGen.Emit(OpCodes.Stloc_0); break;
                case 1: ilGen.Emit(OpCodes.Stloc_1); break;
                case 2: ilGen.Emit(OpCodes.Stloc_2); break;
                case 3: ilGen.Emit(OpCodes.Stloc_3); break;
                default: ilGen.Emit(OpCodes.Stloc_S, index); break;
            }
        }


        public static void ILGen(ConfigClass configInfo)
        {
            MethodBuilder readerFunc = SerializationType.DefineMethod(String.Format("Read{0}FromXml", configInfo.ClassName),
                            MethodAttributes.Public | MethodAttributes.Static,
                            null,
                            new Type[] { typeof(System.Xml.XmlTextReader) });
            var ilGen = readerFunc.GetILGenerator();

            //栈上的临时变量, 依次是:
            //ConfigBase的Instance
            //xml属性字符串
            //ConfigManage的单例
            //while循环的bool
            //break的bool
            var localObject = ilGen.DeclareLocal(configInfo.ClassType);
            var localStrings = new LocalBuilder[configInfo.Nodes.Count];
            int index = 0;
            foreach (var element in configInfo.Nodes)
            {
                localStrings[index] = ilGen.DeclareLocal(typeof(string));
                ++index;
            }

            var managerTypeString = String.Format("XServer.Config.ConfigManager`2[[{0}],[{1}]]",
                configInfo.ClassType.FullName, configInfo.IntKey ? typeof(int).ToString() : typeof(string).ToString());
            var typeInstance = Type.GetType(managerTypeString);
            var addObj = typeInstance.GetMethod("Add");

            var singletonTypeString = String.Format("XServer.SDK.Singleton`1[{0}]", managerTypeString);
            var singletonType = Type.GetType(singletonTypeString);
            var getInstance = singletonType.GetProperty("Instance").GetMethod;

            var instance = ilGen.DeclareLocal(typeInstance);
            var whileBool = ilGen.DeclareLocal(typeof(bool));
            var breakBool = ilGen.DeclareLocal(typeof(bool));

            for (int i = 0; i < localStrings.Length + 1; ++i)
            {
                ilGen.Emit(OpCodes.Ldnull);
                StoreLocal(ilGen, i);
            }

            ilGen.Emit(OpCodes.Call, getInstance);
            ilGen.Emit(OpCodes.Stloc_S, configInfo.Nodes.Count + 1);

            //while循环
            var whileBegin = ilGen.DefineLabel();
            var whileEnd = ilGen.DefineLabel();
            var returnLabel = ilGen.DefineLabel();
            var entryLabel = ilGen.DefineLabel();

            ilGen.Emit(OpCodes.Br, whileEnd);

            ilGen.MarkLabel(whileBegin);

            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call, XmlNodeType);
            ilGen.Emit(OpCodes.Ldc_I4_1);
            ilGen.Emit(OpCodes.Ceq);
            ilGen.Emit(OpCodes.Ldc_I4_0);
            ilGen.Emit(OpCodes.Ceq);
            ilGen.Emit(OpCodes.Stloc_S, configInfo.Nodes.Count + 2);
            ilGen.Emit(OpCodes.Ldloc_S, configInfo.Nodes.Count + 2);
            ilGen.Emit(OpCodes.Brfalse_S, entryLabel);

            ilGen.Emit(OpCodes.Br, returnLabel);


            ilGen.MarkLabel(entryLabel);

            //构造H实体
            var localObjectCtor = configInfo.ClassType.GetConstructor(new Type[0]);
            ilGen.Emit(OpCodes.Newobj, localObjectCtor);
            ilGen.Emit(OpCodes.Stloc, localObject);

            //获取各个字符串
            index = 0;
            foreach (var element in configInfo.Nodes)
            {
                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Ldstr, element.XmlName);
                ilGen.EmitCall(OpCodes.Callvirt, GetAttribute, null);
                ilGen.Emit(OpCodes.Stloc, localStrings[index]);
                LoadLocal(ilGen, index + 1);
                LoadLocal(ilGen, 0);
                ilGen.Emit(OpCodes.Ldflda, configInfo.ClassType.GetField(element.Name));
                ilGen.Emit(OpCodes.Call, methods[element.ElementType]);
                index++;
            }

            ilGen.Emit(OpCodes.Ldloc_S, configInfo.Nodes.Count + 1);
            ilGen.Emit(OpCodes.Ldloc_0);
            ilGen.Emit(OpCodes.Call, addObj);

            ilGen.MarkLabel(whileEnd);

            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call, XmlRead);
            ilGen.Emit(OpCodes.Stloc_S, configInfo.Nodes.Count + 3);
            ilGen.Emit(OpCodes.Ldloc_S, configInfo.Nodes.Count + 3);
            ilGen.Emit(OpCodes.Brtrue, whileBegin);

            ilGen.MarkLabel(returnLabel);
            ilGen.Emit(OpCodes.Ret);
        }

        public static void Save()
        {
            SerializationType.CreateType();
            asm.Save("Xml2Obj.dll");
        }

        public static MethodInfo GetMethod(string typeName)
        {
            var methodName = String.Format("Read{0}FromXml", typeName);
            return SerializationType.GetMethod(methodName);
        }
    }
}
